package com.xiaoxin.experience.util;

import com.xiaoxin.experience.service.net.model.StorageUnitEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * <p>Simple life, simple work, don't complicate</p>
 *
 * @author 小心
 * @version 1.0.0
 * @date 2021/8/29
 */
public class FileUtil
{

    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);

    private static final String MERGE_ERROR_DESCRIBE = "file merge fail : ";

    private FileUtil()  {}

    public static boolean zip(String targetZipFile,String sourceFile)
    {
        try(FileOutputStream fos = new FileOutputStream(targetZipFile);
            ZipOutputStream zos = new ZipOutputStream(fos))
        {
            File fileToZip = new File(sourceFile);
            return zipFile(fileToZip, fileToZip.getName(), zos);
        }
        catch (IOException e)
        {
            log.error("zip file fail: ",e);
        }
        return false;
    }

    private static boolean zipFile(File fileToZip, String fileName, ZipOutputStream zos) throws IOException
    {
        if (fileToZip.isHidden())
        {
            return false;
        }
        if (fileToZip.isDirectory())
        {
            if (fileName.endsWith("/"))
            {
                zos.putNextEntry(new ZipEntry(fileName));
            }
            else
            {
                zos.putNextEntry(new ZipEntry(fileName + "/"));
            }
            zos.closeEntry();
            File[] children = fileToZip.listFiles();
            if (children == null)
            {
                return false;
            }
            for (File childFile : children)
            {
                boolean zipResult = zipFile(childFile, fileName + "/" + childFile.getName(), zos);
                if (!zipResult)
                {
                    return false;
                }
            }
        }
        try (FileInputStream fis = new FileInputStream(fileToZip))
        {
            ZipEntry zipEntry = new ZipEntry(fileName);
            zos.putNextEntry(zipEntry);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0)
            {
                zos.write(bytes, 0, length);
            }
        }
        return true;
    }

    private static void unzip(String zipFilePath, String destDirectory) throws IOException
    {
        try
        {
            File destDir = new File(destDirectory);
            if (!destDir.exists())
            {
                boolean mkdirs = destDir.mkdirs();
                if (mkdirs)
                {
                    log.debug("创建目录[{}]成功",destDirectory);
                }
                else
                {
                    log.error("创建目录[{}]失败",destDirectory);
                    return;
                }
            }
            try (FileInputStream fis = new FileInputStream(zipFilePath);
                 ZipInputStream zis = new ZipInputStream(fis))
            {
                ZipEntry entry = zis.getNextEntry();

                while (entry != null)
                {
                    String filePath = destDirectory + File.separator + entry.getName();
                    if (!entry.isDirectory())
                    {
                        extractFile(zis, filePath);
                    }
                    else
                    {
                        File dir = new File(filePath);
                        boolean mkdir = dir.mkdir();
                        if (mkdir)
                        {
                            log.debug("创建目录[{}]成功",filePath);
                        }
                        else
                        {
                            log.error("创建目录[{}]失败: ",filePath);
                            return;
                        }
                    }
                    zis.closeEntry();
                    entry = zis.getNextEntry();
                }
                zis.closeEntry();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    private static void extractFile(ZipInputStream zis, String filePath) throws IOException
    {
        try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get(filePath))))
        {
            byte[] bytesIn = new byte[4096];
            int read;
            while ((read = zis.read(bytesIn)) != -1)
            {
                bos.write(bytesIn, 0, read);
            }
        }
    }



    public static long getFileByteSize(String filePath)
    {
        File file = new File(filePath);
        if (file.exists())
        {
            return file.length();
        }
        return 0;
    }

    public static long getFileMSize(String filePath)
    {
        return getFileByteSize(filePath)/(1024*1024);
    }

    public static String getHumanFileSize(long fileByteSize)
    {
        double fileByteSizeD = fileByteSize*1.0;
        if (fileByteSize < 1024)
        {
            return StorageUnitEnum.BYTE.toString(fileByteSizeD);
        }
        if (fileByteSize < (1024*1024))
        {
            double fileKbSize = fileByteSizeD/1024;
            return StorageUnitEnum.KB.toString(fileKbSize);
        }
        if (fileByteSize < (1024*1024*1024))
        {
            double fileMbSize = fileByteSizeD/(1024*1024);
            return StorageUnitEnum.MB.toString(fileMbSize);
        }
        if (fileByteSize < (1024*1024*1024*1024L))
        {
            double fileGbSize = fileByteSizeD/(1024*1024*1024);
            return StorageUnitEnum.GB.toString(fileGbSize);
        }
        double fileTbSize = fileByteSizeD/(1024*1024*1024*1024L);
        return StorageUnitEnum.TB.toString(fileTbSize);
    }

    public static void createDirs(String dir)
    {
        File file = new File(dir);

        if (file.isDirectory())
        {
            return;
        }
        boolean fileExist = file.exists();
        if (fileExist)
        {
            deleteFile(file);
        }
        boolean mkdirsResult = file.mkdirs();
        log.info("dir create result: {}",mkdirsResult);
    }

    public static void deleteFile(String dir)
    {
        File dirFile = new File(dir);
        File[] files = dirFile.listFiles();
        if (files != null)
        {
            for(File file : files)
            {
                if (file.isDirectory())
                {
                    deleteFile(file.getAbsolutePath());
                    continue;
                }
                deleteFile(file);
            }
        }
        // 目录为空或者只是一个文件
        deleteFile(dirFile);
    }

    public static void deleteFile(File file)
    {
        if (Objects.isNull(file))
        {
            return;
        }
        try
        {
            Files.delete(file.toPath());
        }
        catch (Exception e)
        {
            log.error("delete file[ " + file.getName() + "] fail: ",e);
        }
    }

    public static String dirPathComplete(String dirPath)
    {
        if (dirPath.endsWith(File.separator) || dirPath.endsWith("/"))
        {
            return dirPath;
        }
        return dirPath + File.separator;
    }

    public static long asyncMerge(String dstFilePath,List<CompletableFuture<File>> fileFutures)
    {
        if (CollectionUtil.isEmpty(fileFutures))
        {
            return 0;
        }
        try(FileOutputStream fileOutputStream = new FileOutputStream(dstFilePath))
        {
            FileChannel outChannel = fileOutputStream.getChannel();
            for(CompletableFuture<File> fileFuture : fileFutures)
            {
                File file = fileFuture.get();
                if (Objects.nonNull(file))
                {
                    transferFrom(outChannel,file);
                }
            }
        }
        catch (Exception e)
        {
            log.error(MERGE_ERROR_DESCRIBE,e);
        }
        return FileUtil.getFileByteSize(dstFilePath);
    }

    public static boolean mergeFile(String dstFilePath,File mergeFile)
    {
        try(FileOutputStream fileOutputStream = new FileOutputStream(dstFilePath))
        {
            FileChannel outChannel = fileOutputStream.getChannel();
            transferFrom(outChannel,mergeFile);
        }
        catch (IOException e)
        {
            log.error(MERGE_ERROR_DESCRIBE,e);
        }
        return false;
    }


    public static void mergeFile(String dstFilePath,List<File> mergeFiles)
    {
        if (CollectionUtil.isEmpty(mergeFiles))
        {
            return;
        }
        try(FileOutputStream fileOutputStream = new FileOutputStream(dstFilePath))
        {
            FileChannel outChannel = fileOutputStream.getChannel();
            for(File mergeFile : mergeFiles)
            {
                transferFrom(outChannel,mergeFile);
            }
        }
        catch (IOException e)
        {
            log.error(MERGE_ERROR_DESCRIBE,e);
        }
    }

    private static void transferFrom(FileChannel outChannel,File file)
            throws IOException
    {
        if (Objects.nonNull(file) && file.exists())
        {
            try(FileInputStream tmpIn = new FileInputStream(file))
            {
                FileChannel inChannel = tmpIn.getChannel();
                outChannel.transferFrom(inChannel,outChannel.size(),inChannel.size());
            }
        }
    }

    public static void writeToFile(String message,String filePath,boolean append)
    {
        writeToFile(message.getBytes(StandardCharsets.UTF_8),filePath,append);
    }

    public static long writeToFile(List<CompletableFuture<byte[]>> futures,String filePath)
    {
        if (CollectionUtil.isEmpty(futures))
        {
            return 0;
        }
        try(FileOutputStream fileOutputStream = new FileOutputStream(filePath,false))
        {
            for(CompletableFuture<byte[]> future : futures)
            {
                try
                {
                    byte[] bytes = future.get();
                    if (Objects.nonNull(bytes))
                    {
                        fileOutputStream.write(bytes);
                        fileOutputStream.flush();
                    }
                }
                catch (Exception e)
                {
                    // ignore
                }
            }
        }
        catch (Exception e)
        {
            return 0;
        }
        return FileUtil.getFileByteSize(filePath);
    }

    public static void writeToFile(List<String> lines,String filePath,boolean append)
    {
        if (CollectionUtil.isEmpty(lines))
        {
            return;
        }
        OpenOption[] openOptions;
        if (append)
        {
            openOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE};
        }
        else
        {
            openOptions = new OpenOption[]{StandardOpenOption.CREATE,StandardOpenOption.WRITE};
        }

        try (BufferedWriter bufferedWriter = Files.newBufferedWriter(new File(filePath).toPath(),openOptions);)
        {
            for (String line : lines)
            {
                bufferedWriter.write(line);
                bufferedWriter.newLine();
            }
            bufferedWriter.flush();
        }
        catch (Exception e)
        {
            log.error("write file fail: ",e);
        }
    }

    public static void writeToFile(byte[] bytes,String filePath,boolean append)
    {
        if (Objects.isNull(bytes))
        {
            return;
        }
        OpenOption[] openOptions;
        if (append)
        {
            openOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE};
        }
        else
        {
            openOptions = new OpenOption[]{StandardOpenOption.CREATE,StandardOpenOption.WRITE};
        }
        try(OutputStream fileOutputStream = Files.newOutputStream(new File(filePath).toPath(),openOptions))
        {
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
        }
        catch (IOException e)
        {
            log.error("write file fail: ",e);
        }
    }

    public static byte[] readAllFileToBytes(String filePath)
    {
        File file = new File(filePath);
        long length = file.length();
        if (length == 0 || length > Integer.MAX_VALUE)
        {
            return new byte[0];
        }
        int size = (int) length;
        byte[] bytes = new byte[size];
        try(FileInputStream fis = new FileInputStream(filePath))
        {
            int read = fis.read(bytes,0,size);
            log.debug("read bytes : [{}]",read);
        }
        catch (Exception e)
        {
            log.error("read file fail :",e);
            return new byte[0];
        }
        return bytes;
    }

    public static List<String> readAsList(String filePath)
    {
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(Paths.get(filePath)), StandardCharsets.UTF_8)))
        {
            return bufferedReader.lines().collect(Collectors.toList());
        }
        catch (IOException e)
        {
            return Collections.emptyList();
        }
    }

    private static String openFileBeforeHandle(String filePath)
    {
        if (StringUtil.isBlank(filePath) || SystemUtil.isLinux())
        {
            return null;
        }
        File file = new File(filePath);
        if (file.isFile())
        {
            try
            {
                // 使用File.getCanonicalPath()来获取文件绝对路径并对路径中存在的.\和..\进行处理
                return file.getCanonicalPath();
            }
            catch (Exception e)
            {
                return null;
            }
        }
        return null;
    }

    public static void openDirAndSelected(String filePath)
    {
        String absolutePath = openFileBeforeHandle(filePath);
        if (Objects.isNull(absolutePath))
        {
            return;
        }
        try
        {
            Runtime.getRuntime().exec("explorer /select, " + absolutePath);
        }
        catch (Exception ignore)
        {}
    }

    public static void openFile(String filePath)
    {
        String absolutePath = openFileBeforeHandle(filePath);
        if (Objects.isNull(absolutePath))
        {
            return;
        }
        try
        {
            Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", absolutePath});
        }
        catch (Exception ignore)
        {}
    }

    public static List<File> prefixFindFile(String dir,String prefix)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> file.getName().startsWith(prefix),result);
        return result;
    }

    public static List<File> suffixFindFile(String dir,String suffix)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> file.getName().endsWith(suffix),result);
        return result;
    }

    public static List<File> fullFindFile(String dir,String fileName)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> file.getName().equals(fileName),result);
        return result;
    }

    public static List<File> fuzzyFindFile(String dir,String fileName)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> file.getName().contains(fileName),result);
        return result;
    }

    public static List<File> regexFindFile(String dir,String regex)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> file.getName().matches(regex),result);
        return result;
    }

    public static void findFile(String dir,FileFilter fileFilter,List<File> result)
    {
        if (!canFindFile(dir,result))
        {
            return;
        }
        if (Objects.isNull(fileFilter))
        {
            result.addAll(listAllFile(dir));
            return;
        }
        File[] files = new File(dir).listFiles();
        if (files != null)
        {
            for (File file : files)
            {
                if (file.isDirectory())
                {
                    findFile(file.getAbsolutePath(),fileFilter,result);
                }
                else
                {
                    if (fileFilter.filter(file))
                    {
                        result.add(file);
                    }
                }
            }
        }
    }

    private static boolean canFindFile(String dir,List<File> result)
    {
        if (StringUtil.isBlank(dir))
        {
            return false;
        }
        if (result.size() > 1000)
        {
            // 避免文件过多导致内存溢出
            return false;
        }
        File dirFile = new File(dir);
        return dirFile.exists() && !dirFile.isFile();
    }

    public static List<File> listAllFile(String dir)
    {
        List<File> result = new ArrayList<>();
        findFile(dir,file -> true,result);
        return result;
    }

    public static String getFileLastModified(File file)
    {
        return DateUtil.formatTimeStd(file.lastModified());
    }

    public static String trimFileExt(String name)
    {
        if (StringUtil.isNotBlank(name))
        {
            int index = name.lastIndexOf(".");
            if (index > 0)
            {
                return name.substring(0,index);
            }
        }
        return null;
    }

    public interface FileFilter
    {
        boolean filter(File file);
    }
}
